/*
 * Copyright (C) 2011-2014 Lannocc Technologies
 * @%@~LICENSE~@%@
 */
package com.iovar.web.dev.trans;

// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.task.Return;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;

// java imports:
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

// 3rd-party imports:
import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.client.*;

/**
 *
 * @author  shawn@lannocc.com
 */
public class Http extends Transport
{
    public Http ()
    {
        
    }
    
    public Http (final String path)
    {
        this (path, null);
    }
    
    public Http (final String path, final Map<String,List<String>> params)
    {
        setPath (path);
        setParams (params);
    }
    
    public Http (final Local local)
    {
        setContext (local.getContext ());

        final String root = (String) context.getInitParameter (Utils.INIT_PARAM_LOOPBACK);
        Log.debug ("root: "+root);
        
        setPath ("//"+root+"/"+context.getContextPath ()+"/"+local.getPath ());
        setParams (local.getParams ());
    }
    
    protected void setPath (final String path)
    {
        this.path = path.replaceAll (" ", "%20");
    }
    
    public InputStream get () throws IOException
    {
        final String query = params!=null ? ("?"+Utils.buildQuery (params)) : "";
        final URL url = new URL (new URL ("http", "", ""), path+query);
        Log.debug ("GET "+split (url));
        final HttpURLConnection http = (HttpURLConnection) url.openConnection ();
        http.setInstanceFollowRedirects (false);
        http.setRequestMethod ("GET");
        
        return http.getInputStream ();
        //return inputErrorCheck (http);
    }
    
    static InputStream inputErrorCheck (final HttpURLConnection http) throws IOException
    {
        final InputStream in = http.getInputStream ();
        final InputStream err = http.getErrorStream ();
        
        if (err!=null || http.getResponseCode () < 200 || http.getResponseCode () >= 300)
        {
            throw new IOException ("status "+http.getResponseCode ());
        }
        
        return in;
    }
    
    public InputStream put (final InputStream data) throws IOException
    {
        final String query = params!=null ? ("?"+Utils.buildQuery (params)) : "";
        final URL url = new URL (new URL ("http", "", ""), path+query);
        Log.debug ("PUT "+split (url));
        final HttpURLConnection http = (HttpURLConnection) url.openConnection ();
        http.setInstanceFollowRedirects (false);
        http.setRequestMethod ("PUT");
        http.setDoOutput (true);
        
        try
        {
            final OutputStream out = http.getOutputStream ();
            for (int b; (b=data.read ())>=0; out.write (b));
        }
        catch (final IllegalArgumentException e)
        {
            Log.error ("cannot put HTTP resource `"+path+"': bad syntax: "+e.getMessage ());
            //setStatus (HttpServletResponse.SC_BAD_REQUEST);
//            Utils.status (resp, resp.SC_BAD_REQUEST, "cannot put HTTP resource `"+path+"': bad syntax: "+e.getMessage ());
        }
        catch (final UnknownHostException e)
        {
            Log.error ("cannot put HTTP resource `"+path+"': unknown host: "+e.getMessage ());
            //setStatus (HttpServletResponse.SC_NOT_FOUND);
//            Utils.status (resp, resp.SC_NOT_FOUND, "cannot put HTTP resource `"+path+"': unknown host: "+e.getMessage ());
        }
        catch (final IOException e)
        {
            Log.error ("cannot put HTTP resource `"+path+"': IOException: "+e.getMessage ());
            //setStatus (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
//            final String error = Utils.getError (http);
//            StringBuffer details = new StringBuffer ("server returned: "+http.getResponseMessage ());
//            if (error!=null) details.append ("\n").append (error);
            
//            Utils.status (resp, http.getResponseCode (), "cannot put HTTP resource `"+path+"': "+details.toString ());
        }
        
        return http.getInputStream ();
    }
    
    public InputStream patch (final InputStream data, final String contentType) throws IOException
    {
        final String query = params!=null ? ("?"+Utils.buildQuery (params)) : "";
        final URL url = new URL (new URL ("http", "", ""), path+query);
        Log.debug ("PATCH "+split (url));
        
        final ByteArrayOutputStream bytes = new ByteArrayOutputStream ();
        
        final CloseableHttpClient http = HttpClients.createDefault ();
        try
        {
            final HttpPatch patch = new HttpPatch (url.toURI ());
            patch.setEntity (new AbstractHttpEntity ()
            {
                public boolean isRepeatable ()
                {
                    return false;
                }

                public long getContentLength ()
                {
                    return -1;
                }

                public boolean isStreaming ()
                {
                    return false;
                }

                public InputStream getContent () throws IOException
                {
                    // should be implemented as well but is irrelevant for this case
                    throw new UnsupportedOperationException ();
                }

                public void writeTo (final OutputStream out) throws IOException
                {
                    for (int b; (b=data.read ())>=0; out.write (b));
                }
            });
            
            http.execute (patch, new ResponseHandler ()
            {
                public Object handleResponse (final HttpResponse resp) throws IOException
                {
                    final StatusLine status = resp.getStatusLine ();
                    if (status.getStatusCode ()<200 || status.getStatusCode ()>=300)
                    {
                        throw new IOException ("non-ok status received: "+status);
                    }
                    
                    final HttpEntity entity = resp.getEntity ();
                    entity.writeTo (bytes);
                    
                    return null;
                }
            });
        }
        catch (final URISyntaxException e)
        {
            Log.error (e);
            throw new IOException (e);
        }
        finally
        {
            http.close ();
        }
        
        return new ByteArrayInputStream (bytes.toByteArray ());
    }
    
    public InputStream delete () throws IOException
    {
        final String query = params!=null ? ("?"+Utils.buildQuery (params)) : "";
        final URL url = new URL (new URL ("http", "", ""), path+query);
        Log.debug ("DELETE "+split (url));
        final HttpURLConnection http = (HttpURLConnection) url.openConnection ();
        http.setInstanceFollowRedirects (false);
        http.setRequestMethod ("DELETE");
        
        return http.getInputStream ();
    }
    
    public Return post (final InputStream data, final String contentType) throws IOException
    {
        final String query = params!=null ? ("?"+Utils.buildQuery (params)) : "";
        final URL url = new URL (new URL ("http", "", ""), path+query);
        Log.debug ("POST "+split (url));
        final HttpURLConnection http = (HttpURLConnection) url.openConnection ();
        http.setInstanceFollowRedirects (false);
        http.setRequestMethod ("POST");
        http.setDoOutput (true);
        
        // can't use null content type because then Tomcat sets it (incorrectly)
        // to application/x-www-form-urlencoded
        http.setRequestProperty("Content-Type", contentType!=null ? contentType : "");
        
        try
        {
            final OutputStream out = http.getOutputStream ();
            Log.debug ("data: "+data);
            /*
            if (data!=null)
            {
                Log.debug ("   XXX: "+(char) data.read ());
                Log.debug ("   XXX: "+(char) data.read ());
                Log.debug ("   XXX: "+(char) data.read ());
            }
            */
            if (data!=null) for (int b; (b=data.read ())>=0; out.write (b));
            out.close ();
        }
        catch (final IllegalArgumentException e)
        {
            Log.error ("cannot exec HTTP resource `"+path+"': bad syntax: "+e.getMessage ());
            //setStatus (HttpServletResponse.SC_BAD_REQUEST);
//            Utils.status (resp, resp.SC_BAD_REQUEST, "cannot exec HTTP resource `"+path+"': bad syntax: "+e.getMessage ());
        }
        catch (final UnknownHostException e)
        {
            Log.error ("cannot exec HTTP resource `"+path+"': unknown host: "+e.getMessage ());
            //setStatus (HttpServletResponse.SC_NOT_FOUND);
//            Utils.status (resp, resp.SC_NOT_FOUND, "cannot exec HTTP resource `"+path+"': unknown host: "+e.getMessage ());
        }
        catch (final FileNotFoundException e)
        {
            Log.error ("cannot exec HTTP resource `"+path+"': not found: "+url.getFile ());
            //setStatus (HttpServletResponse.SC_NOT_FOUND);
//            Utils.status (resp, resp.SC_NOT_FOUND, "cannot exec HTTP resource `"+path+"': not found: "+url.getFile ());
        }
        
        return new Return (http.getContentType (), http.getHeaderField ("Content-Disposition"), http.getInputStream (), new Http.Status (http));
    }
    
    public boolean exists () throws IOException
    {
        final URL url = new URL (new URL ("http", "", ""), path);
        Log.debug ("exists? "+split (url));
        final HttpURLConnection http = (HttpURLConnection) url.openConnection ();
        http.setInstanceFollowRedirects (false);
        http.setRequestMethod ("HEAD");
        
        try
        {
            http.getInputStream ().close ();
        }
        catch (final FileNotFoundException e)
        {
            return false;
        }
        
        return true;
    }
    
    String split (final URL url)
    {
        final StringBuffer sb = new StringBuffer ();
        sb.append (url.getUserInfo ());
        sb.append (" @ ").append (url.getProtocol ());
        sb.append (" : ").append (url.getHost ());
        sb.append (" :").append (url.getPort ());
        sb.append (" ").append (url.getPath ());
        sb.append (" ? ").append (url.getQuery ());
        return sb.toString ();
    }
    
    public static class Status extends com.iovar.web.bin.shell.task.Status
    {
        final Map<String,List<String>> headers;
        final int code;
        
        Status (final HttpURLConnection http) throws IOException
        {
            this.headers = http.getHeaderFields ();
            this.code = http.getResponseCode ();
        }
        
        public boolean affirmative ()
        {
            return (200 <= code && code < 300);
        }
        
        public boolean isRedirect ()
        {
            return (300 <= code && code < 400);
        }
        
        public Map<String,List<String>> getHeaders ()
        {
            return headers;
        }
        
        public String toString ()
        {
            return String.valueOf (code);
        }
    }
}
